﻿/**************************************************************
 * Name:      http_info.cpp
 * Purpose:   HTTP信息获取类
 * Author:    杨卓荦 (yzl@bang5.net)
 * Created:   2010-08-29
 * Copyright: 杨卓荦 (http://www.bang5.net)
 * License:
 **************************************************************/
#include "http_info.h"
#include "url_parser.h" // UrlParser类

#ifdef WIN32
#define strncasecmp strnicmp
#endif
using namespace std;

HttpInfo::HttpInfo(const wxString& full_url, const string& referer) {
  // 将wxString字符串转为标准字符串
  full_url_ = full_url.mb_str(wxConvUTF8);
  ReBuildHtttpInfo(referer);
}

HttpInfo::~HttpInfo() { }

void HttpInfo::ReBuildHtttpInfo(const string& referer) {
  UrlParser url_parser;
  url_parser.SetUrl(full_url_);
  server_ = url_parser.GetServer();
  port_ = url_parser.GetPort();
  file_ = url_parser.GetEscFilePathName();
  host_ = url_parser.GetServer();
  referer_ = referer.empty() ? url_parser.GetRefer() : referer;
  file_name_ = url_parser.GetFileName();
  retry_ = 100;  // 最大重试次数为100
}

bool HttpInfo::GetInfo() {
  char buf[ 1024 ];
  bool is_get = false;
  while (true) {
    if (!Connect(server_, port_)) {
      if ( Retry() ) {  // 连接失败
        continue;
      } else {  // 重试次数达到上限
        return false;
      }
    }

    bool is_request_success = is_get ?
      SendHttpGetRequest(host_.c_str(), file_.c_str(), referer_.c_str()) :
      SendHttpHeadRequest(host_.c_str(), file_.c_str(), referer_.c_str());

    if (!is_request_success) {
      // 发送HEAD请求失败
      if ( Retry() ) {
        continue;
      } else {
        // 重试次数达到上限
        return false;
      }
    }

    // file_ 为从"/"开始的完整路径
    // 如果 file_ = "/dir/list/abc.txt" 则匹配最后一个"/"得到"/dir/list"
    std::string prestr;  // 目录字符串
    size_t last = file_.find_last_of('/');

    if ( last != std::string::npos ) {
      prestr = file_.substr( 0, last );
    }

    int pos = 0;
    int headpos = 0;

    while ( !( headpos = IsHeadEnd( buf, pos ) ) ) {
      int n = Read( buf + pos, 1024 - pos );
      if ( n < 0 ) {
        // 读取HEAD信息失败
        return false;
      }

      if ( n == 0 )
        break;

      pos += n;
    }

    Close();

    buf[ headpos ] = 0;
    int movepos = 0;
    char line[ 256 ];
    int linebyte;
    ret_code_ = -1;  // 重置HTTP状态码
    file_size_ = -1;  // 重置文件大小

    while ( ( linebyte = GetBufLine( buf + movepos, headpos, line ) ) > 0 ) {
      if ( strlen( line ) > 0 ) {
        // 读入一行：line
      }
      movepos += linebyte;

      if ( strncasecmp( line, "HTTP/1.", 7 ) == 0 ) {
        // 获得HTTP响应状态值
        char retcode[4];
        memcpy( retcode, line + 9, 3 );
        retcode[3] = 0;
        ret_code_ = atoi( retcode );
      } else if ( strncasecmp( line, "CONTENT-LENGTH:", 15 ) == 0 ) {
        char slen[ 30 ];
        if(strlen(line + 15) > 25) {
          // 文件长度错误
          return false; // 不再重试
        }
        strcpy( slen, line + 15 );
#ifdef WIN32
        file_size_ = _atoi64(slen);
#else
        sscanf( slen, "%lld", &file_size_ );
#endif
      } else if ( strncasecmp( line, "LOCATION:", 9 ) == 0 ) {
        // 有时地址并不以 http:// 或 ftp:// 开头
        char redirect[ 512 ] = {0};
        memcpy( redirect, line + 9, strlen( line ) - 9 );
        int emp = 0;

        while ( redirect[ emp ] == ' ' ) emp++;
        redirection_ = std::string( redirect + emp );

        is_redirected_ = true;

        if ( strncasecmp( redirection_.c_str(), "HTTP://", 7 ) != 0 &&
             strncasecmp( redirection_.c_str(), "FTP://", 6 ) != 0 ) {
          if( redirection_.length() > 0 && redirection_[0] == '/' ) {
            // 有时重定向为 /dir/to/file 地址
            redirection_ = std::string( "http://" ) + server_ + redirection_;
          } else {  // 非完整URL，构造成完成URL
            redirection_ = std::string( "http://" ) + server_ + prestr +
                           std::string( "/" ) + redirection_;
          }
        }
      } else if ( strncasecmp( line, "SET-COOKIE:", 11 ) == 0 ) {  // 返回 Cookie
        char cookie[ 512 ] = {0};
        int emp = 0, end;
        while ( line[ emp + 11 ] == ' ' ) emp++;
        end = emp;
        while ( line[ end + 11 ] != ';' && line[ end ] ) end++;
        memcpy( cookie, line + 11 + emp, end - emp );
        cookie [ end - emp ] = 0;
        AddCookie( std::string( cookie ) );
      }
    }

    // 处理响应代码
    if ( ret_code_ == -1 ) {
      last_error_ = -202;
      return false;
    }

    // HTTP状态码异常，如404, 500
    if ( ret_code_ >= 400 || ret_code_ < 200 ) {
      // HTTP 状态码错误
      if ( Retry() ) {
        continue;
      } else {
        last_error_ = -203;
        // 重试次数达到上限
        return false;
      }
    }

    // HTTP状态码以2开头，如200
    if ( ret_code_ / 100 == 2 ) {
      if ( file_size_ != -1 ) {
        // 得到文件长度 file_size_
        return true;
      } else {
        last_error_ = -200;
        // 无法得到文件长度信息
        return true;
      }
    }

    // HTTP状态码以3开头，如301、302，重定向
    if ( ret_code_ / 100 == 3 ) {
      if ( is_redirected_ ) {  // 重新定位地址
        referer_ = full_url_;
        full_url_ = redirection_;
        ReBuildHtttpInfo(referer_);
        is_redirected_ = false;
        continue;
      } else if ( file_size_ == -1 && !is_get ) { // 可能是 304 not modified
        is_get = true;
        continue;
      } else {
        last_error_ = -201;
        return false;
      }
    }
  }
  return true;
}

bool HttpInfo::Retry() {
  if ( --retry_ > 0 ) {
    // 等待 retry_wait_ 秒重试
#ifdef WIN32
    Sleep(1000 * retry_wait_);
#else
    sleep(retry_wait_);
#endif
    return true;
  }
  return false;
}

const int HttpInfo::IsHeadEnd(char *buf, int len) const {
  for ( int i = 3; i < len; i++ ) {
    if ( buf[ i - 3 ] == '\r' &&
         buf[ i - 2 ] == '\n' &&
         buf[ i - 1 ] == '\r' &&
         buf[ i - 0 ] == '\n' ) {
      return i;
    }
  }
  return 0;
}

const string& HttpInfo::GetFullURL() const {
  return full_url_;
}

const string& HttpInfo::GetServer() const {
  return server_;
}

const int HttpInfo::GetPort() const {
  return port_;
}

const string& HttpInfo::GetFile() const {
  return file_;
}

const string& HttpInfo::GetHost() const {
  return host_;
}

const string& HttpInfo::GetReferer() const {
  return referer_;
}

const int HttpInfo::GetRetCode() const {
  return ret_code_;
}

const long long int HttpInfo::GetFileSize() const {
  return file_size_;
}

const string& HttpInfo::GetFileName() const {
  return file_name_;
}
